第3章 语言基础(2)

数据类型

ES6有6种简单数据类型(原始数据类型):

和1种复杂数据类型:

可以通过 typeof 操作符查看任意变量是何种数据类型,会返回以下字符串之一:

let message = "some string";
console.log(typeof message); // "string",通过操作符获取类型结果
console.log(typeof(message)); // "string",通过参数的方式获取结果
console.log(typeof 95); // "number"

Undefined 类型

Undefined 类型只有一个值,就是特殊值 undefined。当使用 var 或 let 声明了变量但没有初始化时,就相当于给变量赋予了 undefined 值

对未初始化的变量调用 typeof 时,返回的结果是"undefined",对未声明的变量调用它时, 返回的结果还是"undefined"

let message; // 这个变量被声明了,只是值为undefined 

// let age // 确保没有声明过这个变量

console.log(typeof message); // "undefined"
console.log(typeof age);     // "undefined"
// 这在逻辑上讲是正确的
// 虽然严格来说这两个变量存在根本性差异,但它们都无法执行实际操作

Null 类型

Null 类型同样只有一个值,即特殊值 null,它表示一个空对象指针,因此调用 typeof 时会返回 object

let car = null;
console.log(typeof car);  // "object"

在当前定义一个变量需要未来对它进行赋值的情况下,建议使用 null 进行初始化,这样只要检查这个变量是不是 null 就可知道该变量是否在后来重新进行了赋值

if (car != null) {
    // car 是一个对象的引用
}

undefined 值是由 null 值派生而来的,因此表面上是相等的

console.log(null == undefined);   // true
console.log(null === undefined);  // false

小结1:

Boolean类型

Boolean(布尔值)类型有两个字面值:true 和 false,这两个值不同于数值,因此 true 不等于 1,false 不等于 0。虽然布尔值只有两个,但所有其他类型的值都有相应布尔值的等价形式,可以调用 Boolean() 进行转换:

let message = "Hello world!";
let messageAsBoolean = Boolean(message);

什么值能转换为 true 或 false 的规则取决于数据类型和实际的值:

数据类型 转换为true 转换为false
Boolean true false
String 非空字符串 ""(空字符串)
Number 非零数值(包括无穷值) 0、NaN
Object 任意对象 null
Undefined N/A(不存在) undefined

if 语句会自动将其他类型转换到Boolean类型

let message = "Hello world!";
if (message) {
  console.log("Value is true");
}

Number 类型

// 十进制整数
let intNum = 55;
// 八进制整数,第一个数字必须为0,其余数字为0~7
let octalNum1 = 070; // 八进制的56
let octalNum2 = 079; // 无效的八进制数,忽略前缀0直接当成79处理
let octalNum3 = 0o70; // 严格模式下以0o前缀表示八进制数
// 十六进制整数,以0x前缀表示
let hexNum1 = 0xA; // 十六进制10
let hexNum2 = 0x1f; // 十六进制31
// 浮点数
let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; // 可以忽略开头的0,不推荐
// 因为存储浮点值使用的内存空间是存储整数值的两倍,所以会尽量将浮点数转换为整数
let floatNum4 = 1.; // 小数点后面没有数字,当成整数 1 处理 
let floatNum5 = 10.0; // 小数点后面是零,当成整数 10 处理
// 使用科学计数法处理非常大或非常小的值
// 一个数值(整数或浮点数)后跟一个大写或小写的字母e,再加上一个要乘的10的多少次幂
let floatNum6 = 3.125e7; // 等于31250000
let floatNum7 = 3e-7; // 等于0.0000003

Number.MIN_VALUE 表示JS的最小数值,Number.MAX_VALUE 表示JS的最大数值,超过这个范围的数被表示为 +Infinity 或者 -Infinity

要确定一个值是不是有限大(即介于 JS 能表示的最小值和最大值之间),可以使用 isFinite() 函数

let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result));  // false

有一个特殊的数值叫 NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了

console.log(0 / 0); // NaN
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity

小结2:

有 3 个函数可以将非数值转换为数值:

转换的规则表述起来比较麻烦,感兴趣建议看书上原文。日常应用中除非做面试题,否则一般也用不到,现实工作往往通过相互约定、代码规范或者控制台输出校验来处理。

String 类型

字符串可以使用双引号(")、 单引号(')或反引号(`)标示

let firstName = "John";
let lastName = 'Jacob';
let lastName = `Jingleheimerschmidt`;
let text = "This is the letter sigma: \u03a3."; // 可以包含转义字符
console.log(text.length); // 通过 length 方法获取字符串长度 28

字符串是不可变的(immutable),一旦创建,它的值就不能变了

有两种方式把一个值转换为字符串:

ES6使用反引号(`)定义的字符串可以保留换行字符

// 分别通过转义字符和模板字面量定义字符串
let myMultiLineString = 'first line\nsecond line';
let myMultiLineTemplateLiteral = `first line
second line`;

console.log(myMultiLineString);
// first line
// second line
console.log(myMultiLineTemplateLiteral);
// first line
// second line

// 两种定义方法得到的字符串是一致的
console.log(myMultiLineString === myMultiLinetemplateLiteral); // true

一般用来定义HTML模板

let pageHTML = `
<div>
  <a href="#">
    <span>Jake</span>
  </a>
</div>`;

由于模板字面量会保持反引号内部的空格和换行,所以往往在书写时会因为缩进问题显得格式会怪怪的

// 这个模板字面量虽然看上去整齐,但是实际却包含很多空格
let myTemplateLiteral = `first line
                         second line`;
console.log(myTemplateLiteral.length); // 不一定是多少

// 这个模板字面量以一个换行符开头
let secondTemplateLiteral = `
first line
second line`;
console.log(secondTemplateLiteral[0] === '\n'); // true

// 这个模板字面量没有意料之外的字符,但是编写起来简直是强迫症的天敌
let thirdTemplateLiteral = `first line
second line`;
console.log(thirdTemplateLiteral);
// first line
// second line

模板字面量中的插值语法:通过在 ${} 中使用一个 JS 表达式实现

let value = 5;
let exponent = 'second';
// 以前,字符串插值是这样实现的:
let str1 = value + ' to the ' + exponent + ' power is ' + (value * value);

// 现在,可以用模板字面量这样实现:
let str2 = `${ value } to the ${ exponent } power is ${ value * value }`;
console.log(str1);
console.log(str2);

模板字面量支持标签函数,标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果

let a = 6;
let b = 9;

function simpleTag(strings, aValExpression, bValExpression, sumExpression) {
  console.log(strings);
  console.log(aValExpression);
  console.log(bValExpression);
  console.log(sumExpression);
  return 'foobar';
}

let untaggedResult = `${ a } + ${ b } = ${ a + b }`;
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15

console.log(untaggedResult);
console.log(taggedResult);
// "6 + 9 = 15"
// "foobar"

通过 String.raw 函数获取模板字面量的原始内容

// Unicode 示例
// \u00A9 是版权符号
console.log(`\u00A9`); // ©
console.log(String.raw`\u00A9`); // \u00A9

// 换行符示例
console.log(`first line\nsecond line`); 
// first line
// second line
console.log(String.raw`first line\nsecond line`); 
// "first line\nsecond line"

// 对实际的换行符来说是不行的
// 因为它们不会被转换成转义序列的形式
console.log(String.raw`first line
second line`);
// first line
// second line

Symbol 类型

Symbol 是 ES 6 新增的数据类型,用来创建唯一记号,进而用作非字符串形式的对象属性

符号需要使用 Symbol() 函数初始化,因为符号本身是原始类型,所以 typeof 操作符对符号返回 symbol

let sym = Symbol();
console.log(typeof sym); // symbol

可以传入一个字符串参数作为对符号的描述,将来可以通过这个字符串来调试代码

let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();
let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');

console.log(genericSymbol == otherGenericSymbol);  // false
console.log(fooSymbol == otherFooSymbol);          // false

与其他类型不同的是,Symbol() 函数不能与 new 关键字一起作为构造函数使用

let myBoolean = new Boolean();
console.log(typeof myBoolean); // "object"

let myString = new String();
console.log(typeof myString); // "object"

let myNumber = new Number();
console.log(typeof myNumber); // "object"

let mySymbol = new Symbol(); // TypeError: Symbol is not a constructor

如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。为此,需要使用 Symbol.for() 方法:

let fooGlobalSymbol = Symbol.for('foo');
console.log(typeof fooGlobalSymbol); // symbol
let otherFooGlobalSymbol = Symbol.for('foo'); // 重用已有符号
console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true

可以使用 Symbol.keyFor() 来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键,如果查询的不是全局符号,则返回 undefined

// 创建全局符号
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s)); // foo
// 创建普通符号
let s2 = Symbol('bar');
console.log(Symbol.keyFor(s2)); // undefined
// 如果传给Symbol.keyFor()的不是符号,则该方法抛出 TypeError
Symbol.keyFor(123); // TypeError: 123 is not a symbol

凡是可以使用字符串或数值作为属性的地方,都可以使用符号

let s1 = Symbol('foo'),
        s2 = Symbol('bar'),
        s3 = Symbol('baz'),
        s4 = Symbol('qux');
let o = {
    [s1]: 'foo val'
};
// 这样也可以 o[s1] = 'foo val';
console.log(o); // {Symbol(foo): foo val}

Object.defineProperty(o, s2, {value: 'bar val'});
console.log(o); // {Symbol(foo): foo val, Symbol(bar): bar val}

Object.defineProperties(o, {
    [s3]: {value: 'baz val'},
    [s4]: {value: 'qux val'}
});
console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val,
//  Symbol(baz): baz val, Symbol(qux): qux val}

类似于 Object.getOwnPropertyNames() 返回对象实例的常规属性数组,Object.getOwnPropertySymbols() 返回对象实例的符号属性数组。这两个方法的返回值彼此互斥。Object.getOwnPropertyDescriptors() 会返回同时包含常规和符号属性描述符的对象。Reflect.ownKeys()会返回两种类型的键:

let s1 = Symbol('foo'), s2 = Symbol('bar');
let o = {
    [s1]: 'foo val',
    [s2]: 'bar val',
    baz: 'baz val',
    qux: 'qux val'
};
console.log(Object.getOwnPropertySymbols(o));
// [Symbol(foo), Symbol(bar)]
console.log(Object.getOwnPropertyNames(o));
// ["baz", "qux"]
console.log(Object.getOwnPropertyDescriptors(o));
// {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}}
console.log(Reflect.ownKeys(o));
// ["baz", "qux", Symbol(foo), Symbol(bar)]

小结3:

与其他类型相比,Symbol类型并不常用,但是有些框架或者第三方库中会用到,需要达到看到能认识的程度,等真正遇到了再进一步了解其详细用法即可

Object 类型

Object(对象)是一组数据和功能的集合,通过 new 操作符后跟对象类型的名称来创建。开发者可以通过创建 Object 类型的实例来创建自己的对象,然后再给对象添加属性和方法:

let o1 = new Object();
let o2 = new Object; // 没有参数可省略括号,但不推荐

每个 Object 实例都有如下属性和方法: